﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Reflection;

namespace Xceed.Silverlight.Data
{
  public class PredicateExpressionBuilder
  {
    #region Constructors

    public PredicateExpressionBuilder( FilterExpressionBase expression )
    {
      m_expression = expression;
    }

    #endregion

    public Expression ToExpression( Type itemType )
    {
      MethodInfo methodInfo = PredicateExpressionBuilder.GetTwinGenericMethod(
                                MethodBase.GetCurrentMethod(),
                                itemType );

      return ( Expression )methodInfo.Invoke( this, null );
    }

    public Expression<Func<T, bool>> ToExpression<T>()
    {
      return this.CreatePredicateExpression<T>(
               m_expression,
               typeof( T ),
               false,
               null );
    }

    protected virtual Expression<Func<T, bool>> CreateTautology<T>()
    {
      return ( T item ) => 1 == 1;
    }

    protected virtual Expression<Func<T, bool>> CreateContradiction<T>()
    {
      return ( T item ) => 1 != 1;
    }

    internal Expression<Func<T, bool>> ToExpression<T>(
      Type dataItemUnderlyingType,
      Func<string, Type> getDataMemberType )
    {
      return this.CreatePredicateExpression<T>(
               m_expression,
               dataItemUnderlyingType,
               true,
               getDataMemberType );
    }

    private static Expression GetItemMemberValue(
      ParameterExpression parameter,
      string memberName )
    {
      return parameter.GetMember( memberName );
    }

    private static Func<ParameterExpression, string, Expression> GetRawDataItemMemberValue(
      Type underlyingType,
      Func<string, Type> getDataMemberType )
    {
      return ( ParameterExpression parameter, string memberName ) =>
        {
          // Try to use an interned string to improve string comparison performance.
          string internedMemberName = string.IsInterned( memberName );

          if( internedMemberName != null )
          {
            memberName = internedMemberName;
          }

          Expression<Func<object, string, object>> getItemValueExpression =
            ( item, propertyName ) => ( ( RawDataItem )item ).GetDataMemberValue( propertyName );
          Expression getValue = Expression.Invoke(
                                  getItemValueExpression,
                                  parameter,
                                  Expression.Constant( memberName ) );

          // Find out the member type.
          Type memberType = null;
          if( getDataMemberType != null )
          {
            try
            {
              memberType = getDataMemberType.Invoke( memberName );
            }
            catch( Exception )
            {
              memberType = null;
            }
          }

          if( memberType == null )
          {
            PredicateExpressionBuilder.TryGetMemberType( underlyingType, memberName, out memberType );
          }

          // Cast the returned value to the member type if it is known.
          if( memberType != null && memberType != getValue.Type )
          {
            getValue = Expression.Convert( getValue, memberType );
          }

          return getValue;
        };
    }

    private static bool TryGetMemberType(
      Type elementType,
      string memberName,
      out Type memberType )
    {
      memberType = null;

      if( elementType == null )
        return false;

      try
      {
        memberType = ComplexStringHelper.GetExpressionTypeFromString( memberName, elementType );
      }
      catch( Exception )
      {
        // The exception is swallowed intentionally.
        // The member wasn't found on the type.
      }

      return ( memberType != null );
    }

    private static MethodInfo GetTwinGenericMethod(
      MethodBase methodInfo,
      Type genericType )
    {
      List<Type> parametersType = new List<Type>();
      ParameterInfo[] parametersInfo = methodInfo.GetParameters();

      Debug.Assert( parametersInfo.Length > 0 );

      for( int i = 0; i < parametersInfo.Length - 1; i++ )
      {
        parametersType.Add( parametersInfo[ i ].ParameterType );
      }

      MethodInfo twin = typeof( PredicateExpressionBuilder ).GetMethod(
                          methodInfo.Name,
                          parametersType.ToArray() );

      return twin.MakeGenericMethod( genericType );
    }

    private Expression<Func<T, bool>> CreatePredicateExpression<T>(
      FilterExpressionBase filterExpression,
      Type dataItemUnderlyingType,
      bool isRawDataItem,
      Func<string, Type> getDataMemberType )
    {
      ParameterExpression parameter = Expression.Parameter( typeof( T ), null );
      Func<ParameterExpression, string, Expression> getItemMemberValue;

      if( isRawDataItem )
      {
        getItemMemberValue = PredicateExpressionBuilder.GetRawDataItemMemberValue(
                               dataItemUnderlyingType,
                               getDataMemberType );
      }
      else
      {
        getItemMemberValue = PredicateExpressionBuilder.GetItemMemberValue;
      }

      Func<string, Type> getItemMemberType = getDataMemberType;
      if( getItemMemberType == null )
      {
        Debug.Assert( dataItemUnderlyingType != null );

        getItemMemberType =
          ( memberName ) =>
          {
            return ComplexStringHelper.GetExpressionTypeFromString( memberName, dataItemUnderlyingType);
          };
      }

      LinqExpressionBuilder expressionBuilder = new LinqExpressionBuilder(
                                                  filterExpression,
                                                  parameter,
                                                  getItemMemberValue,
                                                  getItemMemberType );
      Expression body = expressionBuilder.GetExpression();

      if( body == LinqExpressionBuilder.TrueExpression )
      {
        return this.CreateTautology<T>();
      }
      else if( body == LinqExpressionBuilder.FalseExpression )
      {
        return this.CreateContradiction<T>();
      }
      else
      {
        Debug.Assert( body != null );

        return ( Expression<Func<T, bool>> )Expression.Lambda( body, parameter );
      }
    }

    #region Private Fields

    private readonly FilterExpressionBase m_expression; //null

    #endregion
  }
}
